最近按导师的要求做了个小项目:用Android手机跟蓝牙血压计通信。在网上查了很多资料,发现有许多文章是讲解Android蓝牙开发,但是在中文社区缺少针对实现HDP profile的蓝牙健康设备的文章,所以整理了相关知识和代码做一个总结。做了一点微小的贡献,谢谢大家。
1、相关概念
HDP:Health Device Profile顾名思义是针对蓝牙健康设备(如蓝牙血压计、蓝牙体重秤)的一个profile,由蓝牙技术联盟(Bluetooth SIG)发布。并不是所有蓝牙设备都可以采用HDP,只有经典蓝牙(BR/EDR)设备才可以。低功耗蓝牙设备,即蓝牙4.0及以上,采用的是GATT等profile。
上图是HDP的协议栈。在通信时,蓝牙健康设备作为source,Android手机作为sink。图中L2CAP、SDP、MCAP是蓝牙通信的底层协议,中间是IEEE11073协议。11073是IEEE发布的一个健康设备的协议簇,其下包含了许多不同种类的健康设备协议,如11073-10407是血压计的协议。在数据传输时,手机与健康设备根据此协议来发送请求、解析数据等。最上层的application在本文中自然指的就是Android app。
Android蓝牙模块:android.bluetooth是蓝牙的开发包,里面包含了所有蓝牙相关的类,无论是经典蓝牙还是低功耗蓝牙。其中,本文重点关注的是BluetoothHealth这个类,还用到了一些蓝牙基础类BluetoothAdapter BluetoothDevice等,在这里就不展开介绍了。BluetoothHealth是在API14时引入的,所以系统版本高于14的Android手机都可以与采用HDP的健康设备通信。BluetoothHealth中包括了与HDP设备建立通信的API,具体请参见官方文档。在官方文档中有一个建立通信的流程:
1、调用getProfileProxy(Context, BluetoothProfile.ServiceListener, int))来获取代理对象的连接。
2、创建BluetoothHealthCallback回调,调用registerSinkAppConfiguration(String, int, BluetoothHealthCallback))注册一个sink端的应用程序配置。
3、将手机与健康设备配对,这一步一般在手机的“设置”中完成。
4、使用connectChannelToSource(BluetoothDevice, BluetoothHealthAppConfiguration))来建立一个与健康设备的通信channel。有的设备会自动建立通信,不需要在代码中调用这个方法。第二步中的回调会指示channel的状态变化。
5、用ParcelFileDescriptor来读取健康设备传来的数据,并根据IEEE 11073来解析数据。
6、通信结束后,关闭通信channel,注销应用程序配置。
看完这个流程是不是一脸懵逼?不要慌,这里官方文档实在太抽象了,必须要配合实际的代码才能看懂。
2、demo代码
项目的目标是与经典蓝牙血压计进行通信,获取血压计测量的数值和日期。这里血压计的型号是A&D UA-767PBT-C。项目中的代码是以github上这个项目为基础,根据需求进行修改而实现的。
项目结构
上图是项目的结构,非常简单,只有一个activity和一个service。activity与用户交互,service绑定在activity中与蓝牙血压计建立通信、交换数据。
首先AndroidManifest.xml中注册蓝牙权限:
##MainActivity
主要作用是显示血压计传过来的数据,以及绑定服务。
activity的布局如下:
上半部分是一个ArrayList,用于显示手机所配对的蓝牙设备的名称和Mac地址。下半部分会显示一次血压测量的参数:收缩压、舒张压、心率、测量时间。
在MainActivity中一些重要私有变量:
打开蓝牙:
启动服务:
|
|
用handler和message来实现activity与service的通信:
##HDPService
这个service是核心部分,手机与蓝牙血压计的通信就是在此实现的。
service中部分重要私有变量:
在service中按照官方文档的流程一步一步实现与蓝牙血压计的通信。
1、获取代理对象:
代码中的mBluetoothServiceListener是一个回调,如果getProfileProxy方法返回为真,就进入回调。在回调中获取了代理对象属于BluetoothHealth类的proxy。
2、注册BluetoothHealthAppConfiguration:
|
|
mHealthCallback是其中的关键,有两个方法onHealthAppConfigurationStatusChange和onHealthChannelStateChange。
onHealthAppConfigurationStatusChange回调会监听AppConfiguration的状态,会在registerSinkAppConfiguration函数返回后调用。
onHealthChannelStateChange回调会监听与source端(在这里也就是血压计)通信通道的状态变化,如果收到发来的数据,会接收到一个ParcelFileDescriptor用于读取。
3、将手机与健康设备配对
这一步在手机的系统设置中进行,不必严格按照这个流程的顺序,可以事先配对好。
4、建立通信通道
这一步不是所有设备都需要,部分设备会自动建立通道与sink端(手机)进行通信。我们所用的这个蓝牙血压计就是如此,会自动建立通道。
如果不能自动建立通信,可以主动调用如下代码:
mDevice是将要建立通信的source端,可以用mBluetoothAdapter获取与手机配对的任一设备。
5、读取、解析数据
在与蓝牙设备通信时,我们需要知道一点:通信是双向的,设备会向手机发信息,手机可以也需要向设备发信息。在service中建了两个线程,一个用于读数据,一个用于写数据,通过这两个线程实现与设备的数据交换。
首先看读线程
读线程的调用是在回调方法onHealthChannelStateChange中的,当判断通信通道打开时,会开启一个读线程,并且将回调中提供的ParcelFileDescriptor传入读线程的构造方法,用于读取数据。
具体的读取和解析过程我们来看run方法:
while循环中是对读取数据的解析,这里的解析必须参考IEEE 11073中的内容。这个demo用到了血压计,因此就要使用IEEE 11073-10407(专门针对血压计的协议)。使用其他类型的设备也就要使用相应的IEEE 11073-xxxxx协议。
回到demo中的数据读取和解析,这是一个根据IEEE 11073-10407进行手机与蓝牙设备互相发送数据的过程,下图是运行的log:
大致解释一下流程:
按下测量按钮后,蓝牙血压计发来一个e2开头的数据,这是一个请求连接的信号。
收到请求连接后,手机用写线程发送给血压计一个回复同意连接,接着再发送一个数据请求血压计的特征。
血压计发来一个e7开头的数据,回复血压计的特征。
测量完毕后,血压计发来一个e7开头的数据,是这次测量的报告。
手机回复确认收到测量数据。
血压计发来一个e4开头的数据,请求断开连接。
手机回复确认断开连接。
想要查看详细数据请看IEEE 11073-10407!请看IEEE 11073-10407!请看IEEE 11073-10407!
接下去是写线程:
写线程的逻辑非常清晰,就是通过同一个ParcelFileDescriptor向血压计发送数据。发送的数据在这里已经写死,一共四组数据:作用依次是确认连接请求;确认收到测量数据;请求血压计的特征;确认断开连接。
大功告成,收到数据后界面如下:
第一次写文章,有各种不足之处,请大家指正。